#ifndef __CAPACONSULTA_SELECCION_CPP
#define __CAPACONSULTA_SELECCION_CPP

/*****************************************************************************
*                     Organizacion de Datos - 75.06                          *
*                         Catedra Lic. Servetto                              *
*            Facultad de Ingenieria - Universidad de Buenos Aires            *
******************************************************************************/

#include "AdministradorDeOperaciones.h"
#include "AdministradorMetadata.h"
#include "../Comun/Sort.h"
#include <fstream>

using namespace std;
using namespace Comun;

namespace CapaConsulta
{
	void OrdenarExterno(string* archivoResultado, Atributo* ordenarPor, bool asc);	
	list<string>* SeleccionarCampos(list<ClaseAtributo>* campos, Clase* clase, bool exlcuirJoin, string campoJoin);
	CondicionCompuesta* SeleccionarCondiciones(CondicionCompuesta* condiciones, Clase* clase);
	bool LeerRegistros(list<Registro>* buffer, ifstream* archivo, list<string>* nombres);
	Registro* UnirRegistros(Registro* derecho, Registro* izquierdo, list<ClaseAtributo>* campos, string nombreClaseDer,string nombreClaseIzq);
	Registro* AppendRegistros(Registro* derecho, Registro* izquierdo, list<ClaseAtributo>* campos,string nombreClaseIzq);
	Registro* Ordenar(Registro* derecho, list<ClaseAtributo>* campos);

	Seleccion::Seleccion()
	{
		this->NombreArchivoResultado = "resultado.csv";
		CamposSeleccion = new list<string>();
		this->OrdenarAscendente = true;
		OrdernarPor = NULL;
		CampoJoin = NULL;
		Clases = new list<Clase*>();
		Joins = new list<Join>();
		CamposSeleccionConClase = new list<ClaseAtributo>();
		primerJoin = true;
	}

	Seleccion::~Seleccion()
	{
	}

	bool Seleccion::Ejecutar()
	{
		list<string>* aux = new list<string>();

		for(list<Join>::iterator c = Joins->begin();c != Joins->end(); c++)
		{
			c->Leido = false;
			bool fromEncontrado = false;
			bool toEncontrado = false;

			for (list<string>::iterator is = aux->begin(); is!= aux->end(); is++)
			{
				if (*is == c->From.NombreClase)			
					fromEncontrado = true;
				else if (*is == c->ClaseJoin.NombreClase)
					toEncontrado = true;
			}

			if (!fromEncontrado)
			{
				AdministradorMetadata* adm = new AdministradorMetadata(c->From.NombreClase);
				Clase* from = new Clase(*adm->ClaseMetadata);

				Clases->push_back(from);
				aux->push_back(*from->NombreClase);

				delete adm;
			}

			if (!toEncontrado)
			{
				AdministradorMetadata* adm = new AdministradorMetadata(c->ClaseJoin.NombreClase);
				Clase* to = new Clase(*adm->ClaseMetadata);

				Clases->push_back(to);
				aux->push_back(*to->NombreClase);

				delete adm;
			}
		}

		bool presente = false;
		
		for (list<string>::iterator is = aux->begin(); is!= aux->end(); is++)
		{
			if (*is == (*ClaseOperacion->NombreClase))
				presente =true;
		}

		if(!presente)
		{
			Clases->push_back(ClaseOperacion);
			aux->push_back(*ClaseOperacion->NombreClase);			
		}

		Clase* claseFuerte = ClaseConCondicionFuerte();

		if (claseFuerte == NULL)
		{
			claseFuerte = ClaseOperacion;
		}

		// Resolver Join
		EjecutarJoin(claseFuerte);			

		if (OrdernarPor != NULL)
			OrdenarExterno(new string("desordenado.tmp"),OrdernarPor,OrdenarAscendente);

		/*	if ( resultado != NULL)
		delete resultado;*/

		cout << (* resultado->Mensaje) << endl;

		return true;
	}

	list<Registro*>* Seleccion::SeleccionJoin(string claseFuerte, ClaseAtributo claseJoin, ClaseAtributo from,list<Registro*>* aux){
	
		list<Registro*>* aux2 = new list<Registro*>();	
		Clase* der = NULL;

		for (list<Clase*>::iterator clase = Clases->begin(); clase!=Clases->end();clase++)
			//clase join
			if ((*(*clase)->NombreClase) == claseJoin.NombreClase)
				der = *clase;

		der->Fetched = true;

		for (list<Registro*>::iterator regAux = aux->begin(); regAux != aux->end(); regAux++)
		{
			CondicionCompuesta* condicionJoin = new CondicionCompuestaAND();
			CondicionIgual* igualJoin = new CondicionIgual();
			Atributo* CampoJoin = new Atributo();

			for(IteradorDeAtributos a = (*regAux)->Atributos.begin(); a != (*regAux)->Atributos.end();a++)
			{
				if (a->Nombre == from.NombreAtributo)
				{
					CampoJoin->Valor = (*a).Valor.c_str();
					CampoJoin->Nombre = claseJoin.NombreAtributo;
					CampoJoin->Tipo = (*a).Tipo;
				}
			}

			igualJoin->ValorCondicion = *CampoJoin;

			condicionJoin->Subcondiciones.push_back(igualJoin);

			Resultado* resultadoJoin = ConexionCapaMetadata::SeleccionarInstancia(der,
				SeleccionarCampos(CamposSeleccionConClase,der,false,CampoJoin->Nombre),
				condicionJoin);

			if (resultado->Codigo != ExitoConsulta)
				return NULL;

			list<Registro>* bufferJoin = new list<Registro>();
			ifstream* archivoJoin = new ifstream(resultadoJoin->ArchivoResultado->c_str(),ios::in);				

			bool seguir2 = true;
			string line;

			if (!getline(*archivoJoin,line))
			{
				archivoJoin->close();
				continue;
			}

			do
			{
				seguir2 = LeerRegistros(bufferJoin,archivoJoin,SeleccionarCampos(CamposSeleccionConClase,der,false,CampoJoin->Nombre));
				for(IteradorDeRegistros j = bufferJoin->begin(); j != bufferJoin->end(); j++)																		
				{
					if (primerJoin)
						aux2->push_back(UnirRegistros((*regAux),&(*j),CamposSeleccionConClase,claseFuerte,*der->NombreClase));
					else
						aux2->push_back(AppendRegistros((*regAux),&(*j),CamposSeleccionConClase,*der->NombreClase));
				}

			}	
			while (seguir2);

			archivoJoin->close();

			//delete archivoJoin;				
			delete condicionJoin;
			delete bufferJoin;
		}
		aux->clear();

		for (list<Registro*>::iterator auxr = aux2->begin(); auxr!=aux2->end() ; auxr++)
			aux->push_back(*auxr);
		
		delete aux2;		
	}

	list<Registro*>* Seleccion::TraerCamposDeJoin(Registro* i, string claseFuerte)
	{
		list<Registro*>* aux = new list<Registro*>();
		aux->push_back(i);

		for (list<Join>::iterator join = Joins->begin(); join!= Joins->end(); join++)
		{
			if ( (join->From.NombreClase == claseFuerte) && (!join->Leido))			
				SeleccionJoin(claseFuerte,join->ClaseJoin,join->From,aux);
			else if ( (join->ClaseJoin.NombreClase == claseFuerte) && (!join->Leido))
				SeleccionJoin(claseFuerte,join->From,join->ClaseJoin,aux);			

			if (primerJoin)
				primerJoin = false;
		}

		return aux;
	}

	bool Seleccion::EjecutarJoin(Clase* claseFuerte)
	{
		resultado = ConexionCapaMetadata::SeleccionarInstancia(claseFuerte,
			SeleccionarCampos(CamposSeleccionConClase,claseFuerte,false,""),
			SeleccionarCondiciones(Condiciones,claseFuerte));

		if (resultado->Codigo != ExitoConsulta)
			return false;

		list<Registro>* buffer = new list<Registro>();
		ifstream archivoOr(resultado->ArchivoResultado->c_str(),ios::in);
		ofstream* archivoTemp = new ofstream("tempjoin.tmp",ios::out);
		*archivoTemp << archivoOr.rdbuf();
		archivoOr.close();
		archivoTemp->close();
		ifstream* archivo = new ifstream("tempjoin.tmp",ios::in);

		ofstream* archivoJoined = NULL;
		if (OrdernarPor == NULL)
			archivoJoined = new ofstream(this->NombreArchivoResultado.c_str(),ios::out);
		else
			archivoJoined = new ofstream("desordenado.tmp",ios::out);
		
		for (list<ClaseAtributo>::iterator is = CamposSeleccionConClase->begin();is != CamposSeleccionConClase->end() ; is++)
			*archivoJoined << is->NombreClase <<"." << is->NombreAtributo << ";";

		*archivoJoined << endl;

		string line;
		if (!getline(*archivo,line))
			return false;

		bool seguir = true;

		do
		{
			seguir = LeerRegistros(buffer,archivo,SeleccionarCampos(CamposSeleccionConClase,claseFuerte,false,""));

			for(IteradorDeRegistros i = buffer->begin(); i != buffer->end(); i++)
			{
				for (IteradorDeAtributos a1 = i->Atributos.begin();a1 != i->Atributos.end(); a1++)
					{
						for (IteradorDeAtributos a2 = claseFuerte->Miembros->begin();a2 != claseFuerte->Miembros->end(); a2++)
						{
							if (a1->Nombre == a2->Nombre)
								a1->Tipo = a2->Tipo;
						}
					}
				
				if (Joins->size()>0)
				{
					list<Registro*>* resulAux = TraerCamposDeJoin(&(*i),(*claseFuerte->NombreClase));
					if(resulAux != NULL)
					{
						for (list<Registro*>::iterator result = resulAux->begin();result != resulAux->end(); result++)
						{
							bool quedanJoins = true;					
							/*while(true)
							{
							Clase* next;

							for (list<Clase*>::iterator cc = Clases->begin();cc != Clases->begin(); cc++)
							{
							if (!(*cc)->Fetched)
							{
							quedanJoins = true;
							next = (*cc);
							break;
							}
							}

							if (!quedanJoins)
							break;

							TraerCamposDeJoin(					

							}*/

							if (Condiciones->Analizar(*(*result)))
							{
								for (IteradorDeAtributos a = (*result)->Atributos.begin();a != (*result)->Atributos.end(); a++)
									*archivoJoined << a->Valor << ";";

								*archivoJoined << endl;
							}
						}
					}
				}else
				{
					Registro* ordenado = Ordenar(&(*i),CamposSeleccionConClase);
					for (IteradorDeAtributos a = i->Atributos.begin();a != i->Atributos.end(); a++)
								*archivoJoined << a->Valor << ";";

							*archivoJoined << endl;
				}

			}

			archivoJoined->flush();
		}
		while (seguir);

		archivoJoined->close();
		archivo->close();

		delete resultado->ArchivoResultado;
		resultado->ArchivoResultado = new string(this->NombreArchivoResultado.c_str());

		return true;
	}

	Resultado* Seleccion::ObtenerResultado()
	{
		return resultado;
	}

	void Seleccion::OrdenarExterno(string* archivoResultado, Atributo* ordenarPor, bool asc)
	{
		// Llamada a subrutina de Sort Externo
		Sort* s = new Sort(*archivoResultado,*ordenarPor,false,asc);
		s->SortExterno(this->NombreArchivoResultado);

	}

	list<string>* SeleccionarCampos(list<ClaseAtributo>* campos, Clase* clase, bool exlcuirJoin, string campoJoin)
	{
		list<string>* camposSeleccionados = new list<string>();

		for (list<ClaseAtributo>::iterator i = campos->begin(); i != campos->end(); i++)
			for (IteradorDeAtributos a = clase->Miembros->begin(); a != clase->Miembros->end(); a++)
				if (i->NombreClase == (*clase->NombreClase) )
					if ( (a->Nombre == i->NombreAtributo) && ( !exlcuirJoin || campoJoin != i->NombreAtributo) )
						camposSeleccionados->push_back(i->NombreAtributo);

		return camposSeleccionados;
	}

	CondicionCompuesta* SeleccionarCondiciones(CondicionCompuesta* condiciones, Clase* clase)
	{
		CondicionCompuesta* condicionesSeleccionadas;

		if (condiciones->Simbolo == And)
			condicionesSeleccionadas = new CondicionCompuestaAND();
		else
			condicionesSeleccionadas = new CondicionCompuestaOR();

		for (list<Condicion*>::iterator i = condiciones->Subcondiciones.begin(); i != condiciones->Subcondiciones.end(); i++)
			if ((*i)->NombreClase == (*clase->NombreClase) )
				condicionesSeleccionadas->Subcondiciones.push_back(*i);

		return condicionesSeleccionadas;
	}

	bool LeerRegistros(list<Registro>* buffer, ifstream* archivo,list<string>* nombres)
	{
		string line;	
		buffer->clear();

		for (int i = 0; i< 10; i++)		
		{
			if(!getline(*archivo,line))
				return false;

			Registro* r = new Registro();

			int j = 0;
			list<string>::iterator it = nombres->begin();

			while(j < line.size() )
			{
				Atributo* a = new Atributo();
				a->Nombre = *it;

				while (line[j] != ';')			
					a->Valor += line[j++];		

				j++;
				it++;
				r->Atributos.push_back(*a);				
			}

			buffer->push_back(*r);		

		}

		return true;
	}

	Registro* UnirRegistros(Registro* derecho, Registro* izquierdo, list<ClaseAtributo>* campos, string nombreClaseDer,string nombreClaseIzq)
	{
		Registro* aux = new Registro();

		for (list<ClaseAtributo>::iterator i = campos->begin();i != campos->end(); i++)
		{
			bool encontrado = false;

			for (IteradorDeAtributos iad = derecho->Atributos.begin();iad != derecho->Atributos.end() ; iad++)
			{
				if (i->NombreClase == nombreClaseDer)
				{
					if (iad->Nombre == i->NombreAtributo)
					{
						aux->Atributos.push_back(*iad);
						encontrado = true;
						break;					
					}
				}
			}

			if (!encontrado)
			{

				for (IteradorDeAtributos iai = izquierdo->Atributos.begin();iai != izquierdo->Atributos.end() ; iai++)
				{
					if (i->NombreClase == nombreClaseIzq)
					{
						if (iai->Nombre == i->NombreAtributo)
						{
							aux->Atributos.push_back(*iai);				
							break;
						}
					}
				}
			}
		}

		return aux;
	}

	Registro* AppendRegistros(Registro* derecho, Registro* izquierdo, list<ClaseAtributo>* campos, string nombreClaseIzq)
	{
		Registro* aux = new Registro();

		for (IteradorDeAtributos iad = derecho->Atributos.begin();iad != derecho->Atributos.end() ; iad++)
			aux->Atributos.push_back(*iad);

		for (list<ClaseAtributo>::iterator i = campos->begin();i != campos->end(); i++)
		{			
			for (IteradorDeAtributos iai = izquierdo->Atributos.begin();iai != izquierdo->Atributos.end() ; iai++)
			{
				if (i->NombreClase == nombreClaseIzq)
				{
					if (iai->Nombre == i->NombreAtributo)
					{
						aux->Atributos.push_back(*iai);				
						break;
					}
				}
			}
		}

		return aux;
	}

	Registro* Ordenar(Registro* derecho, list<ClaseAtributo>* campos)
	{
		Registro* aux = new Registro();

		for (list<ClaseAtributo>::iterator i = campos->begin();i != campos->end(); i++)
		{
			bool encontrado = false;

			for (IteradorDeAtributos iad = derecho->Atributos.begin();iad != derecho->Atributos.end() ; iad++)
			{				
				if (iad->Nombre == i->NombreAtributo)
				{
					aux->Atributos.push_back(*iad);
					encontrado = true;
					break;
				}
			}
		}

		return aux;
	}


	Clase* Seleccion::ClaseConCondicionFuerte()
	{
		for (list<Condicion*>::iterator i = Condiciones->Subcondiciones.begin(); i != Condiciones->Subcondiciones.end(); i++)
			if ((*i)->Simbolo == Igual)
				for (list<Clase*>::iterator clase = this->Clases->begin(); clase != this->Clases->end(); clase++)
					if ((*i)->NombreClase == (* (*clase)->NombreClase) )
						return (*clase);							

		
		if (Condiciones->Subcondiciones.size() >0)
		{
			Condicion* c = *Condiciones->Subcondiciones.begin();

			for (list<Clase*>::iterator clase = this->Clases->begin(); clase != this->Clases->end(); clase++)
				if (c->NombreClase == (* (*clase)->NombreClase) )
					return (*clase);	
		}

		return NULL;
	}	

}

#endif
